﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// A complex type representing a value that is either an unsigned absolute value or a signed relative value from a logical reference point.
	/// </summary>
	[TypeConverter(typeof(RelativeValueConverter))]
	public struct RelativeValue : IEquatable<RelativeValue>, IComplexProperty {


		#region Members

		private readonly int value;
		private readonly bool relative;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes an instance of the GreenIsomer.Web.Controls.JQuery.RelativeValue type.
		/// </summary>
		/// <param name="relative">If true the value provided is relative to a logical point of reference, otherwise the value provided is absolute and must be unsigned.</param>
		/// <param name="value">The value defined as relative or absolute by the preceding argument.</param>
		/// <exception cref="ArgumentOutOfRangeException">Value cannot be negative unless it is a relative value.</exception>
		public RelativeValue(bool relative, int value) {
			if(!relative && value < 0) throw new ArgumentOutOfRangeException("value", value, "Value cannot be negative unless it is a relative value.");

			this.value = value;
			this.relative = relative;
		}

		#endregion


		#region Properties

		/// <summary>
		/// Returns whether or not the contained value is relative as opposed to absolute.
		/// </summary>
		public bool Relative {
			get { return relative; }
		}

		/// <summary>
		/// Returns the value defined as either relative or absolute.
		/// </summary>
		public int Value {
			get { return value; }
		}

		#endregion


		#region Methods: Object management

		/// <summary>
		/// Determines whether or not the specified object is equal to the current GreenIsomer.Web.Controls.JQuery.RelativeValue object.
		/// </summary>
		/// <param name="obj">The System.Object to compare.</param>
		/// <returns>Whether or not the provided object is equal.</returns>
		public override bool Equals(object obj) {
			if(obj is RelativeValue) return this == (RelativeValue)obj;
			if(obj is int) return this.value == (int)obj && !relative;
			return false;
		}

		/// <summary>
		/// Determines whether or not the specified RelativeValue is equal to the current GreenIsomer.Web.Controls.JQuery.RelativeValue.
		/// </summary>
		/// <param name="other">The GreenIsomer.Web.Controls.JQuery.RelativeValue to compare.</param>
		/// <returns>Whether or not the provided RelativeValue is equal.</returns>
		public bool Equals(RelativeValue other) {
			return this == other;
		}

		/// <summary>
		/// Serves as a hash function for GreenIsomer.Web.Controls.JQuery.RelativeValue.
		/// </summary>
		/// <returns>The hash value associated with the contained data.</returns>
		public override int GetHashCode() {
			if(relative) return ~value.GetHashCode();
			return value.GetHashCode();
		}

		/// <summary>
		/// Returns a System.String that represents the current GreenIsomer.Web.Controls.JQuery.RelativeValue object.
		/// </summary>
		/// <returns>The string representation.</returns>
		public override string ToString() {
			if(!relative || value < 0) return value.ToString(CultureInfo.InvariantCulture);
			return "+" + value.ToString(CultureInfo.InvariantCulture);
		}

		#endregion


		#region Operators

		/// <summary>
		/// Allows an integer to be cast automatically as a GreenIsomer.Web.Controls.JQuery.RelativeValue absolute value.
		/// </summary>
		/// <param name="value">The System.Int32 value to cast.</param>
		/// <returns>The cast RelativeValue.</returns>
		/// <exception cref="ArgumentOutOfRangeException">The value must be unsigned.</exception>
		public static explicit operator RelativeValue(int value) {
			return new RelativeValue(false, value);
		}

		/// <summary>
		/// Compares the equality of two GreenIsomer.Web.Controls.JQuery.RelativeValue types.
		/// </summary>
		/// <param name="value0">The first RelativeValue.</param>
		/// <param name="value1">The second RelativeValue.</param>
		/// <returns>Whether or not the two RelativeValue types have equal values.</returns>
		public static bool operator ==(RelativeValue value0, RelativeValue value1) {
			return value0.relative == value1.relative && value0.value.Equals(value1.value);
		}

		/// <summary>
		/// Compares the inequality of two GreenIsomer.Web.Controls.JQuery.RelativeValue types.
		/// </summary>
		/// <param name="value0">The first RelativeValue.</param>
		/// <param name="value1">The second RelativeValue.</param>
		/// <returns>Whether or not the two RelativeValue types have distinct values.</returns>
		public static bool operator !=(RelativeValue value0, RelativeValue value1) {
			return value0.relative != value1.relative || !value0.value.Equals(value1.value);
		}

		#endregion


		#region Explicit implementation: IPropertyValue

		void IComplexProperty.AddPropertyValue(PropertyCollection properties, string name) {
			if(relative) properties.AddValue(name, ToString());
			else properties.AddValue(name, value);
		}

		#endregion


	}

}